package graduation.project.body;

import graduation.project.exception.NonExpressionException;
import graduation.project.factory.DataAccess;
import graduation.project.factory.MutantGenFactory;
import graduation.project.factory.TestSetGenFactory;
import graduation.project.mutant.strategy.MutantsGenerator;
import graduation.project.store.IMutants;
import graduation.project.store.ITestCases;
import graduation.project.testcase.strategy.TestSetGenerator;
import graduation.project.utilities.Utility;

import java.util.ArrayList;
import java.util.regex.Pattern;

public class OriginExpression extends Expression {

	public static final int ALL = -1;
	private final String originExID;
	private final String idnf;
	private int[][] utpSet; // Note: The set of otpSet and rfpSet may be NULL.
	private int[][][] nfpSet;
	private int[] otpSet;
	private int[] rfpSet;

	public OriginExpression(String expression) throws NonExpressionException {

		super(expression);
		originExID = Integer.toString(expression.hashCode());
		idnf = toIDNF(dnf);
		model = generateModel(idnf);
	}

	public OriginExpression(String expression, String originExID)
			throws NonExpressionException {
		super(expression);
		this.originExID = originExID;
		idnf = toIDNF(dnf);
		model = generateModel(idnf);
	}

	public String getOriginExID() {
		return originExID;
	}

	public String getIDNF() {
		return idnf;
	}

	public int[][] getUtpSet() {
		if (utpSet == null)
			divideSpace();
		return utpSet;
	}

	public int[] getOtpSet() {
		if (otpSet == null)
			divideSpace();
		return otpSet;
	}

	public int[][][] getNfpSet() {
		if (nfpSet == null)
			divideSpace();
		return nfpSet;
	}

	public int[] getRfpSet() {
		if (rfpSet == null)
			divideSpace();
		return rfpSet;
	}

	/**
	 * here stress the expression is dnf form. here we don't decide whether it
	 * is valid. 1. term redundant; 2. literal redundant
	 * 
	 * @param expression
	 * @return
	 */
	public String toIDNF(String expression) {
		StringBuffer originEx = new StringBuffer();
		originEx.append(expression);

		int termCount;
		int plusIndex;
		String term;
		// delete redundant term
		termCount = expression.split("\\+").length;
		for (int i = 0; i < termCount; i++) {
			plusIndex = originEx.indexOf("+");
			if (plusIndex < 0)
				break;
			term = originEx.substring(0, plusIndex);
			originEx.delete(0, plusIndex + 1);
			if (!isRedundancy(term, originEx.toString()))
				originEx.append('+').append(term);
		}
		// delete redundant literal
		termCount = originEx.toString().split("\\+").length;
		int liteCount;
		String literal;
		StringBuffer tempTerm = new StringBuffer();
		for (int i = 0; i < termCount; i++) {
			plusIndex = originEx.indexOf("+");
			if (plusIndex < 0) {
				tempTerm.append(originEx.toString());
				originEx.delete(0, originEx.length());
			} else {
				tempTerm.append(originEx.substring(0, plusIndex));
				originEx.delete(0, plusIndex + 1);
			}
			liteCount = tempTerm.length();
			for (int j = 0; j < liteCount; j++) {
				if (tempTerm.charAt(0) == '!') {
					literal = tempTerm.substring(0, 2);
					tempTerm.delete(0, 2);
					j++;
				} else {
					literal = tempTerm.substring(0, 1);
					tempTerm.delete(0, 1);
				}
				if (!isRedundancy(tempTerm.toString(), originEx.toString()
						+ "+" + literal))
					tempTerm.append(literal);
			}
			if (originEx.length() == 0)
				originEx.append(tempTerm.toString());
			else
				originEx.append("+").append(tempTerm.toString());
			tempTerm.delete(0, tempTerm.length());
		}
		termCount = originEx.toString().split("\\+").length;
		for (int i = 0; i < termCount; i++) {
			plusIndex = originEx.indexOf("+");
			if (plusIndex < 0)
				break;
			term = originEx.substring(0, plusIndex);
			originEx.delete(0, plusIndex + 1);
			if (!isRedundancy(term, originEx.toString()))
				originEx.append('+').append(term);
		}
		return originEx.toString();
	}

	public TestCases generateTestCases(String strategy) {
		return generateTestCases(strategy, ALL);
	}

	public TestCases generateTestCases(String strategy, int number) {

		TestCases tcs = null;
		TestSetGenFactory tsgf = new TestSetGenFactory();
		TestSetGenerator tsg = tsgf.createTestSetGen(strategy);
		if (tsg != null)
			tcs = tsg.generateTestCases(this, number);
		DataAccess dataA = new DataAccess();
		ITestCases iTcs = dataA.createTestCases();
		iTcs.insertTestCases(tcs);
		return tcs;
	}

	public Mutants generateMutants(String faultType) {
		return generateMutants(faultType, ALL);
	}

	public Mutants generateMutants(String faultType, int number) {

		Mutants m = null;
		MutantGenFactory mgf = new MutantGenFactory();
		MutantsGenerator mg = mgf.createGenerator(faultType);
		if (mg != null)
			m = mg.generateMutants(this, number);
		DataAccess dataA = new DataAccess();
		IMutants iM = dataA.createMutants();
		iM.insertMutants(m);
		return m;
	}

	public void divideSpace() {

		String model = generateModel(idnf);
		String[] terms = idnf.split("\\+");
		ArrayList<String> result = new ArrayList<String>();

		int count;
		int firstTrueTerm = -1;
		StringBuffer term = new StringBuffer();
		String nfp;
		int liteIndex;
		for (int i = 0; i < (int) Math.pow(2.0, model.length()); i++) {
			count = 0;
			for (int j = 0; j < terms.length; j++) {
				if (isTermTrue(terms[j], i, model)) {
					count++;
					if (count == 1)
						firstTrueTerm = j;
				}
			}
			if (count == 1 && firstTrueTerm != -1) {
				result.add("utp" + firstTrueTerm);
				firstTrueTerm = -1;
			} else if (count > 1) {
				result.add("otp");
			} else if (count < 1) {
				nfp = "";
				for (int j = 0; j < terms.length; j++) {
					term.append(terms[j]);
					liteIndex = -1;
					for (int k = 0; k < terms[j].length(); k++) {
						liteIndex++;
						if (terms[j].charAt(k) == '!') {
							term.deleteCharAt(k);
							if (isTermTrue(term.toString(), i, model)) {
								nfp += "nfp" + j + ":" + liteIndex + ",";
							}
							term.insert(k, '!');
							k++;
						} else {
							term.insert(k, '!');
							if (isTermTrue(term.toString(), i, model))
								nfp += "nfp" + j + ":" + liteIndex + ",";
							term.deleteCharAt(k);
						}
					}
					term.delete(0, term.length());
				}
				if (nfp.equals("")) {
					result.add("rfp");
				} else
					result.add(nfp);
			}
		}
		// deal with utp, nfp, otp, rfp
		String type;
		StringBuffer otp = new StringBuffer();
		StringBuffer rfp = new StringBuffer();

		String[] utps = new String[terms.length];
		String[][] nfps = new String[terms.length][];
		utpSet = new int[terms.length][];
		nfpSet = new int[terms.length][][];
		for (int i = 0; i < terms.length; i++) {
			type = terms[i].replaceAll("!", "");
			nfpSet[i] = new int[type.length()][];
			nfps[i] = new String[type.length()];
		}

		String[] tl; // temp string array, use to store the midiate data.
		for (int i = 0; i < result.size(); i++) {
			type = (String) result.get(i);
			if (type.indexOf("otp") >= 0)
				otp.append(i).append(",");
			else if (type.indexOf("rfp") >= 0)
				rfp.append(i).append(",");
			else if (type.indexOf("utp") >= 0) {
				int number = Integer.parseInt(type.substring(3));
				if (utps[number] == null)
					utps[number] = "" + i + ",";
				else
					utps[number] += "" + i + ",";
			} else if (type.indexOf("nfp") >= 0) {
				type = type.replaceAll("nfp", "");
				tl = type.replaceAll("nfp", "").split(",");
				for (int j = 0; j < tl.length; j++) {
					int colonPos = tl[j].indexOf("" + ":");
					int termIndex = Integer.parseInt(tl[j].substring(0,
							colonPos));
					int literalIndex = Integer.parseInt(tl[j]
							.substring(colonPos + 1));
					if (nfps[termIndex][literalIndex] == null) {
						nfps[termIndex][literalIndex] = "" + i + ",";
					} else
						nfps[termIndex][literalIndex] += "" + i + ",";
				}
			}
		}

		if (otp.length() > 0) {
			tl = otp.toString().split(",");
			otpSet = new int[tl.length];
			for (int i = 0; i < tl.length; i++)
				otpSet[i] = Integer.parseInt(tl[i]);
		} else
			System.out.println("overlapping true points is empty");

		if (rfp.length() > 0) {
			tl = rfp.toString().split(",");
			rfpSet = new int[tl.length];
			for (int i = 0; i < tl.length; i++)
				rfpSet[i] = Integer.parseInt(tl[i]);
		} else
			System.out.println("remainning false points is empty");

		for (int i = 0; i < utps.length; i++)
			if (utps[i] == null)
				System.out.println("utp(i) is empty");
			else {
				tl = utps[i].split(",");
				utpSet[i] = new int[tl.length];
				for (int j = 0; j < tl.length; j++)
					utpSet[i][j] = Integer.parseInt(tl[j]);
			}

		for (int i = 0; i < nfps.length; i++) {
			for (int j = 0; j < nfps[i].length; j++)
				if (nfps[i][j] == null)
					System.out.println("nfp[i,j] is empty");
				else {
					tl = nfps[i][j].split(",");
					nfpSet[i][j] = new int[tl.length];
					for (int k = 0; k < tl.length; k++)
						nfpSet[i][j][k] = Integer.parseInt(tl[k]);
				}
		}
	}

	private boolean isRedundancy(String term, String restEx) {
		String rest = restEx;
		StringBuffer buffer = new StringBuffer();
		String ex;
		String[] terms;

		// replace expression's literal with "0" or "1", deal with the origin
		// expression
		char liter;
		boolean not = false;
		for (int i = 0; i < term.length(); i++) {
			liter = term.charAt(i);
			if (liter == '!') {
				not = true;
				continue;
			}
			if (not) {
				rest = rest.replaceAll("" + liter, "0");
				not = false;
			} else
				rest = rest.replaceAll("" + liter, "1");
		}
		rest = rest.replaceAll("!1", "0").replaceAll("!0", "1").replaceAll(
				"1+", "1");
		if (Pattern.matches(".*\\+1\\+.*|1\\+.*|.*\\+1|1", rest))
			return true;
		else {
			rest = rest.replaceAll("1", "");
			terms = rest.split("\\+");

			for (int i = 0; i < terms.length; i++)
				if (!(terms[i].equals("")) && terms[i].indexOf("0") < 0)
					buffer.append(terms[i] + "+");
			ex = buffer.toString(); // using ex store the change result.
			if (ex.length() == 0)
				return false;
			if (ex.charAt(ex.length() - 1) == '+')
				ex = ex.substring(0, ex.length() - 1);
			if (isAlwaysTrue(ex)) // decide whether the term is redundancy.{
				return true;
		}
		return false;
	}

	public boolean isAlwaysTrue(String expression) {
		String mod = generateModel(expression);
		for (int i = 0; i < (int) Math.pow(2.0, mod.length()); i++) {
			// System.out.println("isAlwaysTrue: i: " + i);
			if (!evaluate(expression, i, mod))
				return false;
		}
		return true;
	}

	public boolean equals(Object obj) {

		if (!(obj instanceof OriginExpression))
			return false;
		if (obj == null)
			return false;

		if (utpSet == null)
			return super.equals(obj);
		else {
			Expression ex = (Expression) obj;
			if (ex.getExpression().equals(expression)
					|| ex.getDNF().equals(dnf))
				return true;
			for (int i = 0; i < utpSet.length; i++)
				for (int j = 0; j < utpSet[i].length; j++)
					if (ex.evaluate(utpSet[i][j], model) != true)
						return false;

			if (otpSet != null)
				for (int i = 0; i < otpSet.length; i++)
					if (ex.evaluate(otpSet[i], model) != true)
						return false;

			for (int i = 0; i < nfpSet.length; i++)
				for (int j = 0; j < nfpSet[i].length; j++)
					for (int k = 0; k < nfpSet[i][j].length; k++)
						if (ex.evaluate(nfpSet[i][j][k], model) != false)
							return false;

			if (rfpSet == null)
				for (int i = 0; i < rfpSet.length; i++)
					if (ex.evaluate(rfpSet[i], model) != false)
						return false;
			return true;
		}
	}

	/***************************************************************************
	 * Note: the HDValue here is a negative value.
	 * 
	 * @param originEx
	 * @param HDValue
	 * @return
	 */
	public static int[] getHDSetPoints(OriginExpression originEx, int HDValue) {

		int[] result = null;
		if (HDValue >= 1) {
			try {
				OriginExpression origin = new OriginExpression("!("
						+ originEx.getIDNF() + ")");
				return getHDSetPoints(origin, -1 * HDValue);
			} catch (NonExpressionException e) {
				System.out.println("the String is not valid expression.");
				e.printStackTrace();
			}
		}
		if (HDValue == -1) {
			int[][][] nfpSet = originEx.getNfpSet();
			int nfpNum = 0;
			for (int i = 0; i < nfpSet.length; i++)
				for (int j = 0; j < nfpSet[i].length; j++)
					nfpNum += nfpSet[i][j].length;

			result = new int[nfpNum];
			int index = 0;
			for (int i = 0; i < nfpSet.length; i++)
				for (int j = 0; j < nfpSet[i].length; j++)
					for (int k = 0; k < nfpSet[i][j].length; k++)
						result[index++] = nfpSet[i][j][k];
		} else {
			int[][] allHD = getAllHDArray(originEx);
			if (allHD == null || Math.abs(HDValue) >= allHD.length + 2)
				result = null;
			else
				result = allHD[Math.abs(HDValue) - 2];
		}
		return result;
	}

	public static int[][] getAllHDArray(OriginExpression originEx) {

		int[][] result = null;
		String model = originEx.getModel();
		String[] terms = originEx.getIDNF().split("\\+");
		String[] hdAr = new String[model.length() - 1];
		int[] rfpSet = originEx.getRfpSet();

		if (rfpSet == null)
			return null;
		for (int i = 0; i < hdAr.length; i++)
			hdAr[i] = "";

		int minValue;
		int temp;
		for (int i = 0; i < rfpSet.length; i++) {
			minValue = model.length() + 1;
			for (int j = 0; j < terms.length; j++) {
				temp = Utility.getDistanceofCelltoTerm(terms[j], rfpSet[i],
						model);
				if (temp < minValue)
					minValue = temp;
			}
			hdAr[minValue - 2] = hdAr[minValue - 2] + rfpSet[i] + ",";
		}

		if (hdAr[hdAr.length - 1].length() != 0)
			result = new int[hdAr.length][];
		else
			for (int i = 0; i < hdAr.length; i++)
				if (hdAr[i].length() == 0) {
					result = new int[i][];
					break;
				}

		String[] s;
		if (result != null) {
			for (int i = 0; i < result.length; i++) {
				s = hdAr[i].split(",");
				result[i] = new int[s.length];
				for (int j = 0; j < result[i].length; j++)
					result[i][j] = Integer.parseInt(s[j]);
			}
		}
		return result;
	}

	public static void main(String[] args) throws NonExpressionException {
		// OriginExpression origin = new OriginExpression("(a(!d + !e +
		// de!(!fgh!i + !ghi)!(!fglk + !g!ik)) + !(!fgh!i + !ghi)!(!fglk +
		// !g!ik)(b + c!m + f))(a!b!c + !ab!c + !a!bc)");
		// OriginExpression origin = new OriginExpression("abcd");
		OriginExpression origin = new OriginExpression("(a+b)(c+!d)");
		System.out.println("");
		System.out.println("main, dnf: " + origin.getDNF());
		System.out.println("main, idnf: " + origin.getIDNF());
		System.out.println("main, model: " + origin.getModel());
		
		int[][] utps = origin.getUtpSet();
		int[][][] nfps = origin.getNfpSet();
		System.out.println("-----------------------------");
		System.out.println("DisplayUtpSet:");
		if (utps == null)
			System.out.println("Utpset is null");
		else {
			for (int i = 0; i < utps.length; i++) {
				System.out.print("i = " + i + ": ");
				if (utps[i] == null) {
					System.out.println("continue");
					continue;
				}
				for (int j = 0; j < utps[i].length; j++)
					System.out.print(utps[i][j] + " ");
				System.out.println("");
			}
		}
		System.out.println("DisplayNftSet:");
		// for (int i = 0; i < utps.length; i++) {
		// System.out.print("i = " + i + ": ");
		// if (utps[i] == null) {
		// System.out.println("");
		// continue;
		// }
		// for (int j = 0; j < utps[i].length; j++)
		// System.out.print(utps[i][j] + " ");
		// System.out.println("");
		// }
		// System.out.println("nfp:");
		// int[][][] nfps = origin.getNfpSet();
		// for(int i = 0; i < nfps.length; i++){
		// for(int j = 0; j < nfps[i].length; j++){
		// System.out.print("nfps, " + i + ", " + j + ": ");
		// for(int k = 0; k < nfps[i][j].length; k++)
		// System.out.print(nfps[i][j][k] + " ");
		// System.out.println("");
		// }
		// }
		//		
		// int[] rfps = origin.getRfpSet();
		// if(rfps == null)
		// System.out.println("rfp set is empty");
		// else
		// for(int i = 0; i < rfps.length; i++){
		// System.out.print(rfps[i] + ", ");
		// }
		// System.out.println("");
		//		
		// int[] otps = origin.getOtpSet();
		// if(otps == null)
		// System.out.println("otp set is empty");
		// else
		// for(int i = 0; i < otps.length; i++){
		// System.out.print(otps[i] + ", ");
		// }
		// System.out.println("");
	}
}
